package com.liang;

/**
 * 生产者消费者案列
 *  虚假唤醒
 *
 * 一、解决线程安全问题：
 *  1、同步代码块：锁的对象是括号中的对象，所以线程通信的时候，需要用括号中的对象来调用wait,notify,notifyAll方法
 *  2、同步方法：用synchronized来修饰的函数就是同步方法
 *      如果是非静态的同步函数，则锁定的对象是当前函数所在的对象this
 *      如果是静态同步函数，则锁定的是该函数所属的类的字节码文件
 *
 * 二、线程间的通信
 * 1、 wait():  等待   如果线程执行了wait方法，那么该线程会进入等待的状态，等待状态下的线程必须要被其他线程调用notify方法才能唤醒。
 *  一个线程如果执行了wait方法，那么该线程就会进入以锁对象为标识的线程池中进行等待
 *
 * 2、notify()： 唤醒    唤醒线程池等待线程其中的一个。(选择是任意的)
 *  一个线程如果执行了notify方法，那么就会唤醒以锁对象为标识的线程池中的一个等待线程
 *
 * 3、notifyAll() : 唤醒线程池所有等待 线程。
 *
 *
 * @author: Liangxp
 * @date: 2019/7/31 21:21
 */
public class TestProduceAndConsumer7 {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Produce produce = new Produce(clerk);
        Consumer consumer = new Consumer(clerk);

        new Thread(produce, "生产者A").start();
        new Thread(consumer, "消费者B").start();

        new Thread(produce, "生产者C").start();
        new Thread(consumer, "消费者D").start();
    }
}

/**
 * 店员
 */
class Clerk {
    private int product = 0;

    // 进货
    public synchronized void get() {
        // 为了避免虚假唤醒问题，应该总是使用在循环中
        while (product >= 1) {
            try {
                System.out.println("产品已生产进货");
                // 当产品满的时候进入消费者等待状态，被唤醒后继续从这里执行
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+" ： "+ ++product);
        // 唤醒线程池所有等待线程。(售货)
        this.notifyAll();

    }

     // 售货
    public synchronized void sale() {
        while (product <= 0) {
            System.out.println("缺货等待进货");
            try {
                // 等待进货
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " : " + --product);
        this.notifyAll();
    }
}

/**
 * 生产者
 */
class Produce implements Runnable {

    private Clerk clerk;

    public Produce(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20 ; i++) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.get();
        }
    }
}

/**
 * 消费者
 */
class Consumer implements Runnable {

    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.sale();
        }
    }
}


